﻿from __future__ import unicode_literals

import sys, re, struct
import re

from wbox import devinfo

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   
class Operand(object):
    def __init__(self, items):
        self.items = items
        
    def pack(self, dword=False):
        L = []
        for item in self.items:
            data = item.pack(dword).rstrip(b'\x00')
            if len(data) < 2:
                data += b'\x00'
            size = chr(len(data) + 2)
            L.append(size + data + size)
        return b"".join(L)
        
    @property
    def mnemonic(self):
        for item in self.items:
            if item.code < 0xf0:
                return item.mnemonic
                
    def main(self):
        for item in self.items:
            if item.code < 0xf0:
                return item
                
    def __repr__(self):
        return "<{0}: {1}>".format(self.__class__.__name__, repr(self.items))
        
    def __str__(self):
        L = []
        for item in self.items:
            s = str(item)
            L.append(s)
        return "".join(L)

    @staticmethod
    def create(t):
        return Operand(t)

        
class OpItem(object):
    _prefix_  = ["K", "U", "J", "BL", "@"]
    _postfix_ = [".", "V", "Z", "ZZ"]
    _sep_     = ["U", "J", "BL"]
    
    def __init__(self, mnemonic, address, isext=False):
        self.mnemonic = mnemonic.upper()
        self.code, base, bit = devinfo.getCode(self.mnemonic, isext)
        self.ishex = base == 16
        if len(address) > 0:
            if address[0] == "$":
                self.ishex = True
                address = address[1:]
            elif address[0] == "%":
                self.ishex = False
                address = address[1:]
        if len(address) > 0:
            self.value = int(address, [10, 16][self.ishex])
        else:
            self.value = 0
        
    def has_prefix(self):
        return (self.code >= 0xF0) and (self.mnemonic in self._prefix_)
    
    def has_postfix(self):
        return (self.code >= 0xF0) and (self.mnemonic in self._postfix_)
        
    def has_sep(self):
        return (self.code >= 0xF0) and (self.mnemonic in self._sep_)

    def __repr__(self):
        return "<{0}: {1}>".format(self.__class__.__name__, str(self))
        
    def __str__(self):
        if not self.ishex:
            return "{0}%{1}".format(self.mnemonic, self.value)
        else:
            hex = "{0:X}".format(self.value)
            if hex[0] in "ABCDEF":
                hex = "0" + hex
            return "{0}${1}".format(self.mnemonic, hex)
    
    def pack(self, dword=False):
        fmt = b"<Bh" if not dword else b"<Bl"
        if self.ishex:
            fmt = fmt.upper()
        code = self.code
        if dword and self.mnemonic in ["K", "H"]:
            code = self.code + 1
        return struct.pack(fmt, code, self.value)
    

class OpFloat(OpItem):
    def __init__(self, value):
        self.mnemonic = "E"
        self.code, base, bit = devinfo.getCode(self.mnemonic)
        self.value = value
        self.ishex = False
        
    def pack(self, dword=False):
        return struct.pack(b"<Bf", self.code, self.value)
    
    @staticmethod
    def create(t):
        return [OpFloat(float(t[1]))]
    
    
class OpString(OpItem):
    def __init__(self, value):
        self.mnemonic = '"'
        self.code, base, bit = devinfo.getCode(self.mnemonic)
        self.value = value
        
    def pack(self, dword=False):
        return struct.pack(b"B", self.code) + self.value.encode("euc-kr")
        
    def __str__(self):
        return '"{0}"'.format(self.value)
        
    @staticmethod
    def create(t):
        return [OpString(t[1])]
        
        
class OpDevice(OpItem):
    def __init__(self, mnemonic, address):
        OpItem.__init__(self, mnemonic, address, mnemonic in [".", "@"])
        
    @staticmethod
    def create(t):
        try:
            if len(t) == 2:
                return [OpDevice(t[0], t[1])]
            elif len(t) == 4:
                return [OpExtDevice(t[2], t[3]), OpDevice(t[0], t[1])]
            else:
                raise Exception("error item count {0}".format(t))
        except ValueError:
            return []
        except:
            raise
        
        
class OpExtDevice(OpItem):
    def __init__(self, mnemonic, address):
        super(OpExtDevice, self).__init__(mnemonic, address, True)
        
    @staticmethod
    def create(t):
        if len(t) == 2:
            return [OpExtDevice(t[0], t[1])]
        elif len(t) == 4:
            return [OpExtDevice(t[2], t[3]), OpExtDevice(t[0], t[1])]
        else:
            raise "error item count {0}".format(t)
            
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        
SEP     = re.compile(r"([^ \t]+)[\\/]") #([^ \t]+)")
NAME    = r"(BL|D[XY]|F[DXY]|S[MDXYBW]|Z[RZ]|[A-Z\.])"
ADDRESS = r"([\$%]?[+-]?[0-9A-F]+)"
DEVICE  = re.compile(r"(?i)" + NAME + ADDRESS)
REAL    = re.compile(r"(?i)E(.+)")
STRING  = re.compile(r'"(.*)"')
INDEX   = re.compile(r"(?i)(?:ZZ|Z|V)")
POSTFIX = re.compile(r"(?i)(?:ZZ|Z|V|\.)")
AT      = re.compile(r"@")
SPACE   = re.compile(r"[ \t]+")

class InvalidDeviceException(Exception):
    pass

    
class Parser(object):
    def __init__(self):
        self.mstack = []

    def match(self, p):
        self.m = p.match(self.s, self.i)
        return self.m
        
    def value(self):
        return self.m.groups()
        
    def next(self):
        self.i = self.m.end()
        
    def push(self):
        self.mstack.append(self.m)
        
    def pop(self):
        self.m = self.mstack.pop()
       
    def _s(self):
        return self.s[self.i:self.i+10]
        
        

class OperandParser(Parser):
    def parse(self, s, i=0):
        self.s = s
        self.i = i
        result = []
        if self.match(AT):
            result += [OpExtDevice("@", "0")]
            self.next()
        if self.match(SEP):
            self.push()
            t = self._device()
            result += OpExtDevice.create(t)
            self.pop()
            self.next()
        return Operand.create(result + self.device())
        
    def device(self):
        if self.match(REAL):
            self.next()
            return [OpFloat(float(self.m.group(1)))]
        if self.match(STRING):
            self.next()
            return [OpString(self.m.group(1))]
        t = self._device()
        if len(t) > 2 and not POSTFIX.match(t[2]):
            if t[0] == "K":
                return [OpExtDevice("K", t[1])] + OpDevice.create(t[2:])
            else:
                raise InvalidDeviceException("invalid device '{0}'".format(self._s()))
        return OpDevice.create(t)
    
    def _device(self):
        result = ()
        while self.match(DEVICE):
            result += self.value()
            self.next()
        if not result:
            raise InvalidDeviceException("invalid device '{0}'".format(self._s()))
        return result
        
operand = OperandParser()
        
        
class OperandsParser(Parser):
    def parse(self, s, i=0):
        self.s = s
        self.i = i
        end = len(self.s)
        result = []
        while self.i < end:
            result.append(operand.parse(self.s, self.i))
            self.i = operand.i
            self.space()
        return result
        
    def space(self):
        if self.match(SPACE):
            self.next()
            
operands = OperandsParser()
        
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        
def parseop(op):
    return operand.parse(op)
    
def isop(op):
    try:
        return operand.parse(op)
    except InvalidDeviceException:
        return None
    except:
        raise
        
def parseops(op):
    return operands.parse(op)
    
def splitdevicename(device):
    op = operand.parse(device)
    device = op.main()
    return device.mnemonic, device.value

    name, addr = splitdevicename(name)
    code2, base, bit = maincode[name]
    code += code2
    address += int(addr, base)


# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

def testOperand():
    sample = [
        ("K1",             b'\x04\xe8\x01\x04'),
        ("K-1",            b'\x05\xe8\xff\xff\x05'),
        ("K$1",            b'\x04\xe8\x01\x04'),
        ("K1Z0",           b'\x04\xf0\x00\x04\x04\xe8\x01\x04'),
        ("HA",             b'\x04\xea\n\x04'),
        ("HAZ0",           b'\x04\xf0\x00\x04\x04\xea\n\x04'),
        ("E1",             b'\x07\xec\x00\x00\x80?\x07'),
        ("E1.",            b'\x07\xec\x00\x00\x80?\x07'),
        ("E.1",            b'\x07\xec\xcd\xcc\xcc=\x07'),
        ("E.0",            b'\x04\xec\x00\x04'),
        ("M0",             b'\x04\x90\x00\x04'),
        ("K1M0",           b'\x04\xf1\x01\x04\x04\x90\x00\x04'),
        ("K1M0Z0",         b'\x04\xf1\x01\x04\x04\xf0\x00\x04\x04\x90\x00\x04'),
        ("D0",             b'\x04\xa8\x00\x04'),
        ("D0.0",           b'\x04\xf2\x00\x04\x04\xa8\x00\x04'),
        ("@D0",            b'\x04\xf3\x00\x04\x04\xa8\x00\x04'),
        ("@D0Z0",          b'\x04\xf3\x00\x04\x04\xf0\x00\x04\x04\xa8\x00\x04'),
        ("M0Z0",           b'\x04\xf0\x00\x04\x04\x90\x00\x04'),
        ("D0Z0",           b'\x04\xf0\x00\x04\x04\xa8\x00\x04'),
        ("ZR0ZZ0",         b'\x04\xf6\x00\x04\x04\xb0\x00\x04'),
        ("U0",             b'\x04\xd8\x00\x04'),
        ("J1",             b'\x04\xd9\x01\x04'),
        ('"Wolf System"',  b'\x0e\xeeWolf System\x0e'),
        ("U0/G0",          b'\x04\xf8\x00\x04\x04\xab\x00\x04'),
        ("U0Z0/G0",        b'\x04\xf0\x00\x04\x04\xf8\x00\x04\x04\xab\x00\x04'),
        ("U0Z0/G0Z0",      b'\x04\xf0\x00\x04\x04\xf8\x00\x04\x04\xf0\x00\x04\x04\xab\x00\x04'),
        ("FX0",            b'\x04\x9e\x00\x04'),
        ("FY0",            b'\x04\x9f\x00\x04'),
        ("FD0",            b'\x04\xaa\x00\x04'),
        ("BL0/S0",         b'\x04\xfc\x00\x04\x04\x98\x00\x04'),
        ("Y$10C0",         b'\x05\x9d\xc0\x10\x05'),
        ("Y10C0",          b'\x05\x9d\xc0\x10\x05'),
        ]
        
    error = 0
    for device, result in sample:
        op = parseop(device)
        if op.pack() != result:
            error += 1
            print device, repr(result), op, repr(op.pack())
    print "OK" if error == 0 else "ERROR"
    
    if isop("BOX"):
        print "ERROR"
    if isop("CABLE"):
        print "ERROR"
    
    print operands.parse('K1 K1M0 "Wolf System" D0')


if __name__ == '__main__':
    testOperand()
    